use chrono::Local;
use jsonwebtoken::{DecodingKey, Validation};
use salvo::http::StatusCode;
use salvo::writing::Json;
use salvo::{handler, Depot, FlowCtrl, Request, Response};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::sync::LazyLock;
use tokio::sync::RwLock;

use crate::config::api_config::ApiResult;
use crate::config::app_config::CONFIG;
use crate::service::{get_login_user, ContextHolder, CLAIMS};
use crate::vo::sys_menu_vo::MenuVo;

pub static MEMORY_CACHE: LazyLock<RwLock<HashMap<String, String>>> =
    LazyLock::new(|| RwLock::new(HashMap::new()));

#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Claims {
    pub org_id: Option<i64>,
    pub admin: AdminType,
    pub user_name: Option<String>,
    pub user_id: Option<i64>,
    pub role_id: Option<i64>,
    pub menus: Option<Vec<MenuVo>>,
    pub exp: Option<i64>,
}

#[derive(Serialize, Deserialize, Debug, Clone, Copy, Default)]
pub enum AdminType {
    SuperAdmin = 0,
    GeneralAdmin = 1,
    #[default]
    Member = 2,
}

impl From<Option<i64>> for AdminType {
    fn from(value: Option<i64>) -> Self {
        match value {
            Some(val) => match val {
                0 => AdminType::SuperAdmin,
                1 => AdminType::GeneralAdmin,
                2 => AdminType::Member,
                _ => unreachable!("none admin type"),
            },
            None => AdminType::Member,
        }
    }
}

impl Claims {
    pub fn new(
        org_id: Option<i64>,
        admin: AdminType,
        user_id: Option<i64>,
        user_name: Option<String>,
        role: Option<i64>,
        menus: Option<Vec<MenuVo>>,
    ) -> Self {
        let exp = Local::now().timestamp() + CONFIG.server.token.expire * 60;
        Claims {
            org_id,
            admin,
            user_id,
            user_name,
            role_id: role,
            menus,
            exp: Some(exp),
        }
    }
}

/// panic catch handler
#[handler]
pub async fn catcher_error(
    request: &mut Request,
    res: &mut Response,
    depot: &mut Depot,
    ctrl: &mut FlowCtrl,
) {
    let url = request.uri().path_and_query();
    let user = get_login_user(depot).await;
    tracing::error!("error: url = {:?}, user = {:?} ", url, user);
    if let Some(status_code) = res.status_code {
        match status_code {
            StatusCode::NOT_FOUND => {
                ctrl.skip_rest();
                res.status_code = Some(StatusCode::NOT_FOUND);
                res.render(Json(ApiResult::<()> {
                    code: 404,
                    data: None,
                    msg: "Resource Not Found".to_string(),
                }));
            }
            StatusCode::INTERNAL_SERVER_ERROR => {
                ctrl.skip_rest();
                tracing::info!("interlanl server error => {:#?}", res.to_string());
                res.status_code = Some(StatusCode::INTERNAL_SERVER_ERROR);
                res.render(Json(ApiResult::<()> {
                    code: 500,
                    data: None,
                    msg: "Internal Server Error".to_string(),
                }));
            }
            StatusCode::BAD_REQUEST => {
                ctrl.skip_rest();
                tracing::info!("bad_request => {:#?}", res.to_string());
                res.status_code(StatusCode::BAD_REQUEST);
                res.render(Json(ApiResult::<()> {
                    code: 400,
                    data: None,
                    msg: "Bad Request".to_string(),
                }));
            }
            _ => {
                ctrl.skip_rest();
                tracing::info!("error => {:#?}", res.to_string());
                res.status_code(StatusCode::INTERNAL_SERVER_ERROR);
                res.render(Json(ApiResult::<()> {
                    code: 500,
                    data: None,
                    msg: "Server Error".to_string(),
                }));
            }
        }
    }
}

/// auth handler
#[handler]
pub async fn handler_auth(
    request: &mut Request,
    response: &mut Response,
    depot: &mut Depot,
    ctrl: &mut FlowCtrl,
) {
    let token = request.headers().get("authorization");

    if let Some(header_value) = token {
        let token_string = header_value.to_str().unwrap().replace("Bearer ", "");
        if token_string.is_empty() {
            return_no_auth_reresponse(response, ctrl, "no authorize".to_string())
        } else {
            let guard = MEMORY_CACHE.read().await;
            let real_token = guard.get(&token_string);
            if let Some(real) = real_token {
                let secret = &CONFIG.server.token.secret;
                let result = jsonwebtoken::decode::<Claims>(
                    real,
                    &DecodingKey::from_secret(secret.as_bytes()),
                    &Validation::default(),
                );
                match result {
                    Ok(claims_data) => {
                        let current_timestamp = Local::now().timestamp();
                        if let Some(exp) = claims_data.claims.exp {
                            if current_timestamp > exp {
                                return_no_auth_reresponse(
                                    response,
                                    ctrl,
                                    "token expire".to_owned(),
                                );
                            } else {
                                depot.insert(CLAIMS, claims_data.claims);
                                let result = ContextHolder::builder(depot).await;
                                if let Err(err) = result {
                                    log::error!("builder context holder error {:}", err);
                                }

                                // if ctrl.has_next() {
                                //     dbg!("has next?");
                                //     let next = ctrl.call_next(request, depot, response).await;
                                //     dbg!(&next);
                                // }
                            }
                        }
                    }
                    Err(e) => return_no_auth_reresponse(response, ctrl, e.to_string()),
                }
            } else {
                return_no_auth_reresponse(response, ctrl, "no_auth1".to_string())
            }
        }
    } else {
        return_no_auth_reresponse(response, ctrl, "no_auth2".to_string())
    }
}

fn return_no_auth_reresponse(response: &mut Response, ctrl: &mut FlowCtrl, msg: String) {
    ctrl.skip_rest();
    response.status_code = Some(StatusCode::UNAUTHORIZED);
    response.render(Json(ApiResult::<()> {
        code: 401,
        data: None,
        msg,
    }))
}

/// permission handler
#[handler]
pub async fn handler_permission(
    request: &mut Request,
    response: &mut Response,
    depot: &mut Depot,
    ctrl: &mut FlowCtrl,
) {
    let url_path = request.uri().path();
    let claims_ret = get_login_user(depot).await;
    //查询当前用户是否在用农户角色列表中
    if let Ok(claims) = claims_ret {
        let option_menu = &claims.menus;
        if let Some(menu) = option_menu {
            if !is_url_exist(menu, url_path) {
                ctrl.skip_rest();
                response.status_code = Some(StatusCode::FORBIDDEN);
                response.render(Json(ApiResult::<()> {
                    code: 403,
                    data: None,
                    msg: "not permission".to_string(),
                }))
            }
        } else {
            ctrl.skip_rest();
            response.status_code = Some(StatusCode::FORBIDDEN);
            response.render(Json(ApiResult::<()> {
                code: 403,
                data: None,
                msg: "not permission".to_string(),
            }))
        }
    }
}

fn is_url_exist(menus: &Vec<MenuVo>, url: &str) -> bool {
    for menu in menus {
        if url.starts_with(&menu.url.clone().unwrap()) {
            return true;
        }
        if is_url_exist(&menu.children, url) {
            return true;
        }
    }
    false
}
